package nnsom;

import java.io.File;
import java.util.List;

import org.encog.mathutil.randomize.ConsistentRandomizer;
import org.encog.ml.data.MLData;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.data.basic.BasicMLData;
import org.encog.ml.data.basic.BasicMLDataSet;
import org.encog.neural.som.SOM;
import org.encog.neural.som.training.basic.BasicTrainSOM;
import org.encog.neural.som.training.basic.neighborhood.NeighborhoodSingle;
import org.encog.persist.EncogDirectoryPersistence;

import views.SubscribersNNManagerApp;

public class CallsSOM {
	private static double OUTPUT_LOW = -1;
	private static double OUTPUT_HIGH = 1;
	private final static int NUMBER_OF_INPUTS = 9;
	private final static int NUMBER_OF_OUTPUTS = 50;

	List<FeedRecord> feedRecords;
	double SOM_INPUT[][];
	SOM network;
	MLDataSet training;
	public int[] freqs;

	public CallsSOM(List<FeedRecord> records) {
		feedRecords = records;
		network = new SOM(NUMBER_OF_INPUTS, NUMBER_OF_OUTPUTS);
		network.reset(123456);
		// network.getStructure().finalizeStructure();
		// (new ConsistentRandomizer(-1, 1, 123456)).randomize(network);
		freqs = new int[NUMBER_OF_OUTPUTS];
	}

	public CallsSOM(String egFileName, List<FeedRecord> records) {
		feedRecords = records;
		network = (SOM) EncogDirectoryPersistence.loadObject(new File(
				egFileName));
		freqs = new int[NUMBER_OF_OUTPUTS];
	}

	public CallsSOM(String egFileName, int[] freqs) {
		network = (SOM) EncogDirectoryPersistence.loadObject(new File(
				egFileName));
		this.freqs = freqs;
	}

	public void train() {
		train(true, 0.7);
	}

	public void train(boolean verbose, double learningRate) {
		convertFeedRecordsToSOMInput();
		training = new BasicMLDataSet(SOM_INPUT, null);
		BasicTrainSOM train = new BasicTrainSOM(network, learningRate,
				training, new NeighborhoodSingle());
		if (verbose)
			System.out.println();
		int iteration;
		for (iteration = 0; iteration <= 10; iteration++) {
			train.iteration();
			if (verbose)
				System.out.println("Iteration: " + iteration + ", Error:"
						+ train.getError());
		}
		System.out.println();
		for (int i = 0; i < SOM_INPUT.length; i++) {
			MLData data = new BasicMLData(SOM_INPUT[i]);
			if (verbose) {
				System.out.printf("Winner for %s is output %d\n",
						prettyPrint(i), network.winner(data));
			}
			freqs[network.winner(data)]++;
		}
	}

	public void persistSOM(String fileName) {
		EncogDirectoryPersistence.saveObject(new File(fileName), network);
	}

	public void printOutputFrequencies() {
		System.out.println("\nFrequencies of each neuron wins");
		for (int i = 0; i < NUMBER_OF_OUTPUTS; i++)
			System.out.printf("neuron %d --> %d\n", i, freqs[i]);
	}

	private String prettyPrint(int i) {
		return String.format(
				"[%5.2f %5.2f %5.2f %5.2f %5.2f %5.2f %5.2f %5.2f]",
				SOM_INPUT[i][0], SOM_INPUT[i][1], SOM_INPUT[i][2],
				SOM_INPUT[i][3], SOM_INPUT[i][4], SOM_INPUT[i][5],
				SOM_INPUT[i][6], SOM_INPUT[i][7], SOM_INPUT[i][8]);
	}

	private void convertFeedRecordsToSOMInput() {
		// System.out.println("Training data size = " + feedRecords.size());
		SOM_INPUT = new double[feedRecords.size()][9];
		int i = 0;
		for (FeedRecord fr : feedRecords) {
			SOM_INPUT[i][0] = fr.quarter;
			SOM_INPUT[i][1] = fr.nbr_calls;
			SOM_INPUT[i][2] = fr.on_weekend;
			SOM_INPUT[i][3] = fr.avg_call_duration;
			SOM_INPUT[i][4] = fr.perc_free;
			SOM_INPUT[i][5] = fr.perc_fixed;
			SOM_INPUT[i][6] = fr.perc_international;
			SOM_INPUT[i][7] = fr.perc_premium;
			SOM_INPUT[i][8] = fr.perc_hidden;
			i++;
		}
		normalizeSOM_INPUT();
	}

	private void normalizeSOM_INPUT() {
		int M = SOM_INPUT.length;
		double input_low, input_high;
		for (int j = 0; j < 9; j++) {
			input_low = SOM_INPUT[0][j];
			input_high = SOM_INPUT[0][j];
			for (int i = 0; i < M; i++) {
				if (SOM_INPUT[i][j] > input_high)
					input_high = SOM_INPUT[i][j];
				if (SOM_INPUT[i][j] < input_low)
					input_low = SOM_INPUT[i][j];
			}
			for (int i = 0; i < M; i++) {
				SOM_INPUT[i][j] = normalize(SOM_INPUT[i][j], input_low,
						input_high);
			}
		}
	}

	private void convertFeedRecordsToSOMInput(String input_low_values,
			String input_high_values) {
		// System.out.println("Training data size = " + feedRecords.size());
		SOM_INPUT = new double[feedRecords.size()][9];
		int i = 0;
		for (FeedRecord fr : feedRecords) {
			SOM_INPUT[i][0] = fr.quarter;
			SOM_INPUT[i][1] = fr.nbr_calls;
			SOM_INPUT[i][2] = fr.on_weekend;
			SOM_INPUT[i][3] = fr.avg_call_duration;
			SOM_INPUT[i][4] = fr.perc_free;
			SOM_INPUT[i][5] = fr.perc_fixed;
			SOM_INPUT[i][6] = fr.perc_international;
			SOM_INPUT[i][7] = fr.perc_premium;
			SOM_INPUT[i][8] = fr.perc_hidden;
			i++;
		}
		normalizeSOM_INPUT(input_low_values, input_high_values);
	}

	private void normalizeSOM_INPUT(String input_low_values,
			String input_high_values) {
		int M = SOM_INPUT.length;
		String s1[] = input_low_values.split(";");
		String s2[] = input_high_values.split(";");
		for (int i = 0; i < M; i++) {
			for (int j = 0; j < 9; j++) {
				SOM_INPUT[i][j] = normalize(SOM_INPUT[i][j],
						Double.parseDouble(s1[j]), Double.parseDouble(s2[j]));
			}
		}
	}

	private double normalize(double value, double input_low, double input_high) {
		double result = ((value - input_low) / (input_high - input_low))
				* (OUTPUT_HIGH - OUTPUT_LOW) + OUTPUT_LOW;
		if (Double.isNaN(result))
			return 0;
		else
			return result;
	}

	public void processLiveData(FeedRecord fr) {
		System.out.println(fr);
		feedRecords.add(fr);
		int R = feedRecords.size();
		SOM_INPUT = new double[R][9];
		convertFeedRecordsToSOMInput();
		MLData data = new BasicMLData(SOM_INPUT[R - 1]);
		System.out.printf("Winner for %s is output %d\n", prettyPrint(R - 1),
				network.winner(data));
		System.out.printf("Frequency of result is %d\n",
				freqs[network.winner(data)]);
		if (freqs[network.winner(data)] == 0) {
			System.out.println("This should be fraud!!!");
		}
	}

	public void processFraudData1() {
		System.out
				.println("100 calls of duration 1 sec to international destinations");
		FeedRecord fr = new FeedRecord();
		fr.quarter = 1; // 7:00-13:00
		fr.nbr_calls = 100;
		fr.avg_call_duration = 1;
		fr.perc_free = 0;
		fr.perc_international = 1;
		fr.perc_premium = 0;
		fr.perc_fixed = 0;
		fr.perc_hidden = 0;
		fr.on_weekend = -1;
		processLiveData(fr);
	}

	public void processFraudData2() {
		System.out
				.println("30 calls of duration 100 sec to premium destination at night and weekend");
		FeedRecord fr = new FeedRecord();
		fr.quarter = 0;
		fr.nbr_calls = 30;
		fr.avg_call_duration = 100;
		fr.perc_free = 0;
		fr.perc_international = 0;
		fr.perc_premium = 1;
		fr.perc_fixed = 0;
		fr.perc_hidden = 0;
		fr.on_weekend = 1;
		processLiveData(fr);
	}

	public void setFeedRecords(List<FeedRecord> feed_records) {
		this.feedRecords = feed_records;
	}

	public void testFraud(String input_low_values, String input_high_values,
			double alertThreshold) {
		int R = feedRecords.size();
		SOM_INPUT = new double[R][9];
		convertFeedRecordsToSOMInput(input_low_values, input_high_values);
		for (int i = 0; i < R; i++) {
			System.out.println(feedRecords.get(i));
			MLData data = new BasicMLData(SOM_INPUT[i]);
			int winner = network.winner(data);
			double x = (double) freqs[winner] / (double) getFeedRecordsNo();
			System.out.printf("neuron won %d having frequency %.2f%%\n",
					winner, x * 100);
			if (x < alertThreshold) {
				System.out
						.println("[ALERT] Calls made during this period deviate from past recorded behavior !!!");
			}
		}
	}

	public int getFeedRecordsNo() {
		int sum = 0;
		for (int i = 0; i < freqs.length; i++) {
			sum += freqs[i];
		}
		return sum;
	}
}
